package com.SpringAOP.test_1_31_springaop;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Component;

@SpringBootApplication
public class Test131SpringAopApplication {

    public static void main(String[] args) {
        SpringApplication.run(Test131SpringAopApplication.class, args);

        // ***** 什么是Spring AOP *****
        // AOP 是指面向切面变成
        // 就如 OOP 面向对象编程
        // 这两者所面向的 维度 不同

        // 那么什么是面向切面编程呢? 切面就是指某一类特定问题, 所以 AOP 也可以理解为面向特定方法的编程
        // 什么是面向特定方法编程呢?  ⽐如"登录校验", 就是⼀类特定问题. 登录校验拦截器, 就
        //是对"登录校验"这类问题的统⼀处理. 所以, 拦截器也是AOP的⼀种应⽤. AOP是⼀种思想, 拦截器是AOP
        //思想的⼀种实现. Spring框架实现了这种思想, 提供了拦截器技术的相关接⼝.
        //同样的, 统⼀数据返回格式和统⼀异常处理, 也是AOP思想的⼀种实现.

        //简单来说: AOP是⼀种思想, 是对某⼀类事情的集中处理
        // 既然是思想那就说明实现方式有很多

        // 那么Spring AOP 就是 Spring 对这一思想的实现, 并提供了一些API

        // 现在有⼀些业务的执⾏效率⽐较低, 耗时较⻓, 我们需要对接⼝进⾏优化.
        // 第⼀步就需要定位出执⾏耗时⽐较⻓的业务⽅法, 再针对该业务⽅法来进⾏优化
        // 如何定位呢? 我们就需要统计当前项⽬中每⼀个业务⽅法的执⾏耗时.
        //如何统计呢? 可以在业务⽅法运⾏前和运⾏后, 记录下⽅法的开始时间和结束时间, 两者之差就是这个⽅
        //法的耗时.
        // 但是一个项目中包含很多模块, 每个模块又有很多接口, 一个接口又包含很多方法, 如果都需要进行计时, 那将会是一项很大的任务量

        // AOP就可以做到在不改动这些原始⽅法的基础上, 针对特定的⽅法进⾏功能的增强.
        //AOP的作⽤：在程序运⾏期间在不修改源代码的基础上对已有⽅法进⾏增强（⽆侵⼊性: 解耦）

        // 接下来在图书管理系统展示
        // 需要先引入引⼊AOP依赖

        // 得出
        // 代码⽆侵⼊: 不修改原始的业务⽅法, 就可以对原始的业务⽅法进⾏了功能的增强或者是功能的改变
        //• 减少了重复代码
        //• 提⾼开发效率
        //• 维护⽅便

        /// **** AOP的详细解释 ****
        // AOP的核心概念:
/*      @Aspect  // 表示这是一个切面类
        @Component
        public class TimeAspect {
            @Around("execution(* com.Book.test_1_25_springmvcbook.Controller.*.*(..))")
            // 表示作用域, 作用方式, AOP在哪个环节起作用, 以及对哪些方法起作用
            public Object timeCost(ProceedingJoinPoint joinPoint) throws Throwable {
            // ProceedingJoinPoint joinPoint 表示的是目标方法
                long star = System.currentTimeMillis();
                Object result = joinPoint.proceed(); // 执行目标方法
                long end = System.currentTimeMillis();
                log.info("消耗时间:"+(end - star)+"ms");
                return result;
            }
        }*/


        // **** AOP的概念分四个(理解) ****
        // 1.切点 : execution(* com.Book.test_1_25_springmvcbook.Controller.*.*(..))
        // 切点(Pointcut), 也称之为"切⼊点"
        //Pointcut 的作⽤就是提供⼀组规则 (使⽤ AspectJ pointcut expression language 来描述), 告诉程序对
        //哪些⽅法来进⾏功能增强
        // 就是一组规则, 通过表达式来描述

        // 2.连接点: ProceedingJoinPoint .
        // 目标方法就是连接点, 或者是切点描述的方法

        // 3.通知 : 具体的逻辑 , 要做什么处理
        // 4.切面 : 就是切点 + 通知

        // 一个类可以有多个切面

        // 接下来详解:
        // **** 五种通知类型 ****
        // @Around: 环绕通知, 此注解标注的通知⽅法在⽬标⽅法前, 后都被执⾏, 且必须要有返回值
        //• @Before: 前置通知, 此注解标注的通知⽅法在⽬标⽅法前被执⾏
        //• @After: 后置通知, 此注解标注的通知⽅法在⽬标⽅法后被执⾏, ⽆论是否有异常都会执⾏
        //• @AfterReturning: 返回后通知, 此注解标注的通知⽅法在⽬标⽅法后被执⾏, 有异常不会执⾏
        //• @AfterThrowing: 异常后通知, 此注解标注的通知⽅法发⽣异常后执⾏

        // 接下来学习切点
        // 可以把 切点提出来定义

        // 定义切面的优先级 @order
        // 再aspect 中展示
        // 可以看出 Order 中的数字越小Before越先执行, 同时 After 越后执行

        // 切点表达式的写法
        // 切点表达式常⻅有两种表达⽅式
        //1. execution(....): 根据方法的签名来匹配
        // execution(* com.Book.test_1_25_springmvcbook.Controller.*.*(..))

        // 表达式 : execution(<访问修饰符> <返回类型> <包名.类名.⽅法(⽅法参数)> <异常>)
        // 其中: 访问修饰符和异常可以省略
        // 切点表达式⽀持通配符表达:
        //1. * ：匹配任意字符，只匹配⼀个元素(返回类型, 包, 类名, ⽅法或者⽅法参数)
        //a. 包名使⽤ * 表⽰任意包(⼀层包使⽤⼀个*)
        //b. 类名使⽤ * 表⽰任意类
        //c. 返回值使⽤ * 表⽰任意返回值类型
        //d. ⽅法名使⽤ * 表⽰任意⽅法
        //e. 参数使⽤ * 表⽰⼀个任意类型的参数
        //2. .. ：匹配多个连续的任意符号, 可以通配任意层级的包, 或任意类型, 任意个数的参数
        //a. 使⽤ .. 配置包名，标识此包以及此包下的所有⼦包
        //b. 可以使⽤ .. 配置参数，任意个任意类型的参数

        //2. @annotation(....): 根据注解来匹配
        //execution表达式更适⽤有规则的, 如果我们要匹配多个⽆规则的⽅法呢, ⽐如：TestController中的t1()
        //和UserController中的u1()这两个⽅法.
        //这个时候我们使⽤execution这种切点表达式来描述就不是很⽅便了.
        //我们可以借助⾃定义注解的⽅式以及另⼀种切点表达式 @annotation 来描述这⼀类的切点

        // 实现步骤：
        //1. 编写⾃定义注解 (先实现一个 自定义的注解)
        //2. 使⽤ @annotation 表达式来描述切点
        //3. 在连接点的⽅法上添加⾃定义注解 (在要用的方法上添加上这个自定义注解)

        //@Target 标识了 Annotation 所修饰的对象范围, 即该注解可以⽤在什么地⽅
        // 常⽤取值:
        //ElementType.TYPE: ⽤于描述类、接⼝(包括注解类型) 或enum声明
        //ElementType.METHOD: 描述⽅法
        //ElementType.PARAMETER: 描述参数
        //ElementType.TYPE_USE: 可以标注任意类型

        // @Retention 指Annotation被保留的时间⻓短, 标明注解的⽣命周期
        // @Retention 的取值有三种
        // 1. RetentionPolicy.SOURCE：表⽰注解仅存在于源代码中, 编译成字节码后会被丢弃.
        // 这意味着在运⾏时⽆法获取到该注解的信息, 只能在编译时使⽤.
        // ⽐如 @SuppressWarnings , 以及lombok提供的注解 @Data , @Slf4j
        //2. RetentionPolicy.CLASS：编译时注解. 表⽰注解存在于源代码和字节码中, 但在运⾏时会被丢
        //弃. 这意味着在编译时和字节码中可以通过反射获取到该注解的信息, 但在实际运⾏时⽆法获取.
        // 通常⽤于⼀些框架和⼯具的注解.
        //3. RetentionPolicy.RUNTIME：运⾏时注解. 表⽰注解存在于源代码, 字节码和运⾏时中. 这意味着在编译时,
        // 字节码中和实际运⾏时都可以通过反射获取到该注解的信息.
        // 通常⽤于⼀些需要在运⾏时处理的注解, 如Spring的 @Controller @ResponseBody

        // Spring AOP的实现⽅式(常⻅⾯试题)
        //1. 基于注解 @Aspect
        //2. 基于⾃定义注解
        //3. 基于Spring API
        //4. 基于代理来实现


        // ******* Spring AOP 原理 *******
        // 先讲讲 @Aspect 注解
        // Spring 使用了 aspectj 的注解, 自己进行了实现.
        // 对于 @Aspect 的实现 还是 Spring 进行了实现
        // 但对于 aspectj 的实现就不是

        //  呢么 SpringAOP 是如何实现的?
        // 是基于 动态代理 实现的
        // 将动态代理的时候, 先引入 代理模式
        // 代理模式有两种: 1.静态代理模式, 和 2.动态代理模式
        // (比如 租房子的时候, 中介 就是一个代理, 本来应该就是 租户直接去找房东, 现实的流程是: 租户 先找 房屋中介 中介再找房东)

        // 代理模式的定义 : 为其他对象提供⼀种代理以控制对这个对象的访问. 它的作⽤就是通过提供⼀个代理类, 让我们
        //               在调⽤⽬标⽅法的时候, 不再是直接对⽬标⽅法进⾏调⽤, ⽽是通过代理类间接调⽤

        // 使用代理前:
        //      如  租户 --> 中介 --> 房东
        //               中介: 叫代理对象         房东: 就是 目标对象, 也叫被代理的对象
        // 没有代理的时候 调用方直接调用 目标对象
        // 有了代理之后, 调用方通过代理对象来调用目标对象

        // 这种模式就是称作: 代理模式

        // 静态:
        // 比如: 答疑老师, 静态就是 在学生问问题之前先分配一个老师进行答疑, 如果还有别的学生要问问题, 需要继续分配一个老师
        // 在程序运行前, 代理对象就已经对目标进行了步骤的预执行代码

        // 动态:
        // 就是不提前进行分配, 只有在学生问问题的时候进行分配

        // 代理模式的主要角色:
        // 1. Subject: 业务接⼝类. 可以是抽象类或者接⼝(不⼀定有)
        //2. RealSubject: 业务实现类. 具体的业务执⾏, 也就是被代理对象.
        //3. Proxy: 代理类. RealSubject的代理.
        // ⽐如房屋租赁
        // Subject 就是提前定义了房东做的事情, 交给中介代理, 也是中介要做的事情
        // RealSubject: 房东
        // Proxy: 中介

        // 静态代理: 由程序员创建代理类或特定⼯具⾃动⽣成源代码再对其编译, 在程序运⾏前代理类的
        //.class ⽂件就已经存在了.
        //• 动态代理: 在程序运⾏时, 运⽤反射机制动态创建⽽成

        // 静态代理
        // 静态代理: 在程序运⾏前, 代理类的 .class⽂件就已经存在了. (在出租房⼦之前, 中介已经做好了相关的
        //⼯作, 就等租⼾来租房⼦了)

        // 接下来通过代码演示 代理模式
        // 以房屋租聘为例
        // 在 proxy 里面展示, 代理的意思
        // 展示的是静态代理
        // 从上述程序可以看出, 虽然静态代理也完成了对⽬标对象的代理, 但是由于代码都写死了, 对⽬标对象的
        //每个⽅法的增强都是⼿动完成的，⾮常不灵活. 所以⽇常开发⼏乎看不到静态代理的场景.


        // 动态代理
        // 不需要针对每个⽬标对象都单独创建⼀个代理对象, ⽽是把这个创建代理对象的⼯作推迟到程序运
        //⾏时由JVM来实现. 也就是说动态代理在程序运⾏时, 根据需要动态创建⽣成.

        // 动态代理常见的有两种实现方式:
        // 1. JDK 动态代理
        // 2. CGLIB动态代理 (这是一个第三方的)

        // 接下来在 dynamicProxy 里面演示动态代理
        // 先看由 JDK 实现的代理

        // 不需要去联系, 理解就行了
        // 企业中所用到的动态代理是很少的

        // 演示之后会发现:  JDK 动态代理 只能代理接口 不能代理类

        // 接下来展示 CGLIB动态代理
        // 在使用之前要添加相关的依赖
        // 在cglibProxy包里面演示

        // 代理的意义: 就是对目标对象进行功能增强
        //AOP的意义也是对目标对象的功能增强

        // **** springAOP 的源码 (了解) ****


        // *****  面试题 ****
        // 0. SpringAOP 的实现方式是什么?
        // 1.SpringAOP 是怎么实现的? 基于动态代理实现的
        // 2.动态代理是怎么实现的, Spring 动态代理是基于JDK 和 CGlib 实现的
        // 3. Spring 使用的是哪个? 两个都用了
        // 4. 什么时候 使用JDK 什么时候使用 CGLib? (这就要看源码了)
        // 代理类的时候使用 CGLib
        // 代理接口的时候既可以使用 JDK 也可以使用 CGLib
        // 但是在 Spring 2.X 版本以后, 默认使用CGLib
        // 可以通过配置项 spring.aop.proxy-target-class=false 来进⾏修改,设置默认为jdk代理

        // proxyTargetClass ⽬标对象 代理⽅式
        // false 实现了接⼝ jdk代理
        // false 未实现接⼝(只有实现类) cglib代理
        // true 实现了接⼝ cglib代理
        // true 未实现接⼝(只有实现类) cglib代理.

        // SpringBoot设置 @EnableAspectJAutoProxy ⽆效, 因为Spring Boot 默认使⽤
        //AopAutoConfiguration进⾏装配

        // Spring对于AOP的实现，基本上都是靠 AnnotationAwareAspectJAutoProxyCreator 去完成
        //⽣成代理对象的逻辑在⽗类 AbstractAutoProxyCreator 中

        // 5. JDK 和 CGLib 有什么区别



    }

}
